package jmine.tec.web.front.domain;

import static javax.persistence.FetchType.LAZY;

import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.sql.Blob;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;
import javax.persistence.Transient;
import javax.persistence.Version;

import jmine.tec.persist.annotation.Alias;
import jmine.tec.persist.annotation.Index;
import jmine.tec.web.front.dao.BackOfficeRequestPropertyDAO;
import jmine.tec.web.front.dao.BackOfficeRequestPropertySystemIdentifier;
import jmine.tec.web.front.dao.BackOfficeRequestTypeDAO;
import jmine.tec.web.front.dao.BackOfficeRequestTypeSystemIdentifier;

import org.apache.commons.lang.builder.CompareToBuilder;
import org.hibernate.Hibernate;
import org.hibernate.annotations.Cascade;
import org.hibernate.annotations.CascadeType;
import org.hibernate.annotations.Type;
import org.hibernate.validator.NotNull;

import bancosys.tec.component.Documentation;
import bancosys.tec.datadigester.input.ResetableInputStreamFactory;
import bancosys.tec.persist.Persistable;
import bancosys.tec.persist.bussobj.PersistableBusinessObject;
import bancosys.tec.persist.dao.BaseDAO;
import bancosys.tec.persist.exception.PersistenceException;
import bancosys.tec.utils.date.Date;
import bancosys.tec.utils.date.Timestamp;

/**
 * A command that is requested by a user to be executed remotely
 * 
 * @author lundberg
 */
@Entity
@Alias("BCOFRQ")
@Table(name = "BACK_OFFICE_REQUEST")
@Documentation("UMA REQUISICAO A SER ENVIADA AO BACK OFFICE")
@SequenceGenerator(sequenceName = "SEQ_BCOFRQ", name = "SEQ_BCOFRQ")
public class BackOfficeRequest extends PersistableBusinessObject {

    private Long id;

    private Long version;

    private String requestingUser;

    private Timestamp requestTimestamp;

    private BackOfficeRequestStatus status;

    private BackOfficeRequestType type;

    private Blob form;

    private Set<BackOfficeRequestValue<?>> values = new HashSet<BackOfficeRequestValue<?>>();

    private transient InputStream cachedForm;

    private Long formReferenceValue;

    /**
     * @return conteudo da execucao.
     */
    @Transient
    public InputStream getFormContent() {
        if (this.cachedForm == null) {
            this.initCachedInputStream();
        }
        if (this.cachedForm != null) {
            try {
                this.cachedForm.reset();
            } catch (IOException e) {
                throw new PersistenceException(e);
            }
        }
        return this.cachedForm;
    }

    /**
     * Inicializa o InputStream
     */
    public void initCachedInputStream() {
        if (this.getForm() != null) {
            try {
                this.cachedForm = ResetableInputStreamFactory.createResetableInputStream(this.getForm().getBinaryStream());
                this.cachedForm.mark(Integer.MAX_VALUE);
            } catch (SQLException e) {
                throw new PersistenceException(e);
            }
        }
    }

    /**
     * Define conteudo de execucao.
     * 
     * @param stream conteudo da execucao.
     */
    public void setFormContent(InputStream stream) {
        try {
            this.setForm(Hibernate.createBlob(ResetableInputStreamFactory.createResetableInputStream(stream)));
        } catch (IOException e) {
            throw new PersistenceException(e);
        }
    }

    /**
     * @return the id
     */
    @Id
    @GeneratedValue(generator = "SEQ_BCOFRQ")
    @Column(name = "COD_BACK_OFFICE_REQUEST", nullable = false)
    @Documentation("CODIGO DA REQUISICAO AO BACK OFFICE.")
    public Long getId() {
        return this.id;
    }

    /**
     * @return the version
     */
    @Version
    @Column(name = "VERSION", nullable = false)
    @Documentation("VERSAO DO REGISTRO.")
    public Long getVersion() {
        return this.version;
    }

    /**
     * @return the requestingUser
     */
    @NotNull
    @Column(name = "USU_REQ", nullable = false)
    @Documentation("USUARIO QUE CRIOU A REQUISICAO.")
    public String getRequestingUser() {
        return this.requestingUser;
    }

    /**
     * @return the requestTimestamp
     */
    @NotNull
    @Type(type = "bancosys.tec.persist.hibernate.type.TimestampType")
    @Column(name = "TS_REQ", nullable = false)
    @Documentation("DATA E HORA EM QUE A REQUISICAO FOI FEITA.")
    public Timestamp getRequestTimestamp() {
        return this.requestTimestamp;
    }

    /**
     * @return the status
     */
    @NotNull
    @Index(suffix = "JT_1")
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "COD_BACK_OFFICE_STATUS", nullable = false)
    @Documentation("CODIGO DO STATUS DA REQUISICAO AO BACK OFFICE.")
    public BackOfficeRequestStatus getStatus() {
        return this.status;
    }

    /**
     * @return the type
     */
    @NotNull
    @Index(suffix = "JT_0")
    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "COD_BACK_OFFICE_REQUEST_TYPE", nullable = false)
    @Documentation("CODIGO DO TIPO DE REQUISICAO.")
    public BackOfficeRequestType getType() {
        return this.type;
    }

    /**
     * @return the form
     */
    @Column(name = "COMMAND_FORM", nullable = false)
    @Documentation("CONTEUDO DO FORMULARIO DA REQUISICAO.")
    protected Blob getForm() {
        return this.form;
    }

    /**
     * @return the values
     */
    @OneToMany(mappedBy = "request")
    @Cascade({ CascadeType.ALL, CascadeType.DELETE_ORPHAN })
    protected Set<BackOfficeRequestValue<?>> getValues() {
        return this.values;
    }

    /**
     * @return the formReferenceValue
     */
    @Column(name = "FRM_REF", nullable = true)
    @Documentation("Valor que referencia um form específico.")
    public Long getFormReferenceValue() {
        return this.formReferenceValue;
    }

    /**
     * @param id the id to set
     */
    public void setId(Long id) {
        this.id = id;
    }

    /**
     * @param version the version to set
     */
    public void setVersion(Long version) {
        this.version = version;
    }

    /**
     * @param requestingUser the requestingUser to set
     */
    public void setRequestingUser(String requestingUser) {
        this.requestingUser = requestingUser;
    }

    /**
     * @param requestTimestamp the requestTimestamp to set
     */
    public void setRequestTimestamp(Timestamp requestTimestamp) {
        this.requestTimestamp = requestTimestamp;
    }

    /**
     * @param status the status to set
     */
    public void setStatus(BackOfficeRequestStatus status) {
        this.status = status;
    }

    /**
     * @param type the type to set
     */
    public void setType(BackOfficeRequestType type) {
        this.type = type;
    }

    /**
     * @param form the form to set
     */
    protected void setForm(Blob form) {
        this.form = form;
    }

    /**
     * @param values the values to set
     */
    protected void setValues(Set<BackOfficeRequestValue<?>> values) {
        this.values = values;
    }

    /**
     * @param value o valor que referencia um form.
     */
    public void setFormReferenceValue(Long value) {
        this.formReferenceValue = value;
    }

    /**
     * Adds a request value
     * 
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestValue<?> value) {
        value.setRequest(this);
        this.getValues().add(value);
    }

    /**
     * Adds a request value of type string
     * 
     * @param property property
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestProperty property, String value) {
        this.addRequestValue(new BackOfficeRequestValueString(property, value));
    }

    /**
     * Adds a request value of type big decimal
     * 
     * @param property property
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestProperty property, BigDecimal value) {
        this.addRequestValue(new BackOfficeRequestValueBigDecimal(property, value));
    }

    /**
     * Adds a request value of type date
     * 
     * @param property property
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestProperty property, Date value) {
        this.addRequestValue(new BackOfficeRequestValueDate(property, value));
    }

    /**
     * Defines request type by identifier
     * 
     * @param typeIdentifier typeIdentifier
     */
    public void setType(BackOfficeRequestTypeSystemIdentifier typeIdentifier) {
        this.setType(this.getDAOByClass(BackOfficeRequestTypeDAO.class).findBySystemIdentifier(typeIdentifier));
    }

    /**
     * Adds a request value of type string
     * 
     * @param propertyIdentifier propertyIdentifier
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestPropertySystemIdentifier propertyIdentifier, String value) {
        this.addRequestValue(this.getDAOByClass(BackOfficeRequestPropertyDAO.class).findBySystemIdentifier(propertyIdentifier), value);
    }

    /**
     * Adds a request value of type big decimal
     * 
     * @param propertyIdentifier propertyIdentifier
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestPropertySystemIdentifier propertyIdentifier, BigDecimal value) {
        this.addRequestValue(this.getDAOByClass(BackOfficeRequestPropertyDAO.class).findBySystemIdentifier(propertyIdentifier), value);
    }

    /**
     * Adds a request value of type date
     * 
     * @param propertyIdentifier propertyIdentifier
     * @param value value
     */
    public void addRequestValue(BackOfficeRequestPropertySystemIdentifier propertyIdentifier, Date value) {
        this.addRequestValue(this.getDAOByClass(BackOfficeRequestPropertyDAO.class).findBySystemIdentifier(propertyIdentifier), value);
    }

    /**
     * Finds a dao by it's class
     * 
     * @param daoClass daoClass
     * @return dao
     * @param <T> dao type
     * @param <P> persistable type
     */
    @Transient
    private <T extends BaseDAO<P>, P extends Persistable> T getDAOByClass(Class<T> daoClass) {
        return this.getController().getDAOFactory().getDAOByClass(daoClass);
    }

    /**
     * @return sorted request values list
     */
    @Transient
    public List<BackOfficeRequestValue<?>> getRequestValues() {
        List<BackOfficeRequestValue<?>> requestValues = new ArrayList<BackOfficeRequestValue<?>>(this.getValues());
        Collections.sort(requestValues, new Comparator<BackOfficeRequestValue<?>>() {
            public int compare(BackOfficeRequestValue<?> o1, BackOfficeRequestValue<?> o2) {
                return new CompareToBuilder().append(BackOfficeRequest.this.getId(), o2.getId()).toComparison();
            }
        });
        return requestValues;
    }

    /**
     * Gets a value of the give property
     * 
     * @param expectedType expectedType
     * @param property property
     * @param <T> value type
     * @return value
     */
    public <T> T getRequestValue(Class<T> expectedType, BackOfficeRequestPropertySystemIdentifier property) {
        for (BackOfficeRequestValue<?> value : this.getValues()) {
            if (value.getProperty().isIdentifiedBy(property)) {
                return expectedType.cast(value.getValue());
            }
        }
        return null;
    }

    /**
     * Gets a value of the give property
     * 
     * @param expectedType expectedType
     * @param property property
     * @param <T> value type
     * @return value
     */
    public <T> T getRequestValue(Class<T> expectedType, BackOfficeRequestProperty property) {
        for (BackOfficeRequestValue<?> value : this.getValues()) {
            if (value.getProperty().equals(property)) {
                return expectedType.cast(value.getValue());
            }
        }
        return null;
    }

}
